var Fall = function( id, rowCount, columnCount ) {

	this.colors = [ 'black' , 'blue' , 'cyan' , 'green' , 'purple' , 'red' , 'white' , 'yellow' ];


	this.setId = function( id ) {
		this.id_ = id;
	};

	this.getId = function() {
		return this.id_;
	};

	this.setRowCount = function( rowCount ) {
		this.rowCount_ = rowCount;
	};

	this.getRowCount = function() {
		return this.rowCount_;
	};

	this.setColumnCount = function( columnCount ) {
		this.columnCount_ = columnCount;
	};

	this.getColumnCount = function() {
		return this.columnCount_;
	};

	this.createBoard = function() {
		var fall_all = Util.newNode( 'div', {className:'fall_all'} );
		this.fall_main = Util.newNode( 'div', {className:'fall_main',bg_offset:0} );
		fall_all.appendChild( this.fall_main );

		// controls 

		var controls        = Util.newNode( 'div',    {className:'fall_controls'} );
		var center_controls = Util.newNode( 'center', {className:'ofall_controls'} );
		var score_label     = Util.newNode( 'div',    {className:'fall_score_label'} );
		this.score_field    = Util.newNode( 'div',    {className:'fall_score_field'} );
		var buy_button      = Util.newNode( 'button', {className:'fall_buy_button'} );
		this.buy_field      = Util.newNode( 'div',    {className:'fall_buy_field'} );
		this.hint_button    = Util.newNode( 'button', {className:'fall_hint_button'} );
		this.sound_button   = Util.newNode( 'div',    {className:'sound_on',sound:'true'} );
		this.new_button     = Util.newNode( 'button', {className:'fall_new_button'} );
		controls.appendChild( center_controls );
		center_controls.appendChild( score_label );
		center_controls.appendChild( this.score_field );
		center_controls.appendChild( buy_button );
		center_controls.appendChild( this.buy_field );
		center_controls.appendChild( this.hint_button );
		center_controls.appendChild( this.sound_button );
		center_controls.appendChild( this.new_button );
		score_label.appendChild( Util.newText( 'Score' ) );
		this.score_field.appendChild( Util.newText( '0' ) );
		buy_button.appendChild( Util.newText( 'Buy' ) );
		this.buy_field.appendChild( Util.newText( '0' ) );
		this.hint_button.appendChild( Util.newText( 'hint' ) );
		this.new_button.appendChild( Util.newText( 'new game' ) );
		this.fall_main.appendChild( controls );

		// table for balls

		var table = Util.newNode( 'table', {className:'fall_table'} );
		var tbody = Util.newNode( 'tbody' );
		table.appendChild( tbody );
		this.cells = [];
		for ( var i = 0 ; i < this.getRowCount() ; i++ ) {
			this.cells.push( [] );
			var row = Util.newNode( 'tr', {className:'fall_row'} );
			tbody.appendChild( row );
			for ( var j = 0 ; j < this.getColumnCount() ; j++ ) {
				var klass = 'fall_cell_' + ( 0 == ( i + j ) %2 ? 'even' : 'odd' );
				var td = Util.newNode( 
					'td'
					, {className:klass,klass:klass} 
				);
				var cell = Util.newNode( 'div' , {fall_i:i,fall_j:j} );
				td.appendChild( cell );
				this.cells[ i ].push( cell );
				row.appendChild( td );
			}
		}
		this.fall_main.appendChild( table );

		// notification popup

		this.notify_div = Util.newNode( 'div', {className:'fall_notify_div'} );
		var notify_center = Util.newNode( 'center' );
		this.notify_title = Util.newNode( 'div', {className:'fall_notify_title' } );
		this.notify_title.appendChild( Util.newText( 'Oops' ) );
		var notify_ok_button = Util.newNode( 'button', {className:'fall_notify_ok_button'} );
		notify_ok_button.appendChild( Util.newText( 'bummer' ) );
		this.notify = Util.newNode( 'div', {className:'fall_notify_text'} );

		this.notify_div.appendChild( notify_center );
		notify_center.appendChild( this.notify_title );
		notify_center.appendChild( this.notify );
		notify_center.appendChild( notify_ok_button );

		fall_all.appendChild( this.notify_div );

		// new game popup

		this.new_game_div = Util.newNode( 'div', {className:'fall_new_game_div'} );
		var new_game_center = Util.newNode( 'center' );
		this.new_game_title = Util.newNode( 'div', {className:'fall_notify_title' } );
		this.new_game_title.appendChild( Util.newText( 'Are you sure?' ) );
		var new_game_button_div = Util.newNode( 'div' );
		var new_game_ok_button = Util.newNode( 'button', {className:'fall_notify_ok_button'} );
		new_game_ok_button.appendChild( Util.newText( 'ok' ) );
		var new_game_cancel_button = Util.newNode( 'button', {className:'fall_notify_ok_button'} );
		new_game_cancel_button.appendChild( Util.newText( 'cancel' ) );

		this.new_game_div.appendChild( new_game_center );
		new_game_center.appendChild( this.new_game_title );
		new_game_center.appendChild( Util.newText( 'start a new game?' ) );
		new_game_center.appendChild( new_game_button_div );
		new_game_button_div.appendChild( new_game_ok_button );
		new_game_button_div.appendChild( new_game_cancel_button );

		fall_all.appendChild( this.new_game_div );

		// score popup

		this.score_popup = Util.newNode( 'center', {className:'fall_score_div'});
		fall_all.appendChild( this.score_popup );

		// wire it into the page

		Util.id( this.getId() ).appendChild( fall_all );

		// setup the clickerz

		var thiz = this;
		tbody.onclick = function( e ) { thiz.click( e ); };
		this.hint_button.onclick = function( e ) { thiz.hint(); };
		buy_button.onclick = function( e ) { thiz.buyIt(); };
		notify_ok_button.onclick = function() { Util.hide( thiz.notify_div ); };

		this.new_button.onclick = function( e ) { Util.show( thiz.new_game_div ); };
		new_game_ok_button.onclick = function( e ) { thiz.newGame(); Util.hide( thiz.new_game_div ); };
		new_game_cancel_button.onclick = function( e ) { Util.hide( thiz.new_game_div ); };

		this.sound_button.onclick = function( e ) { thiz.toggleSound(); };

		// sound effects (really not sure about this... )

		this.sound = Util.newNode( 'div', {className:'shorty'} );
		fall_all.appendChild( this.sound );
	};

	this.isSoundOn = function() {
		return ( 'true' == this.sound_button.getAttribute( 'sound' ) );
	};

	this.toggleSound = function() {
		if ( this.isSoundOn() ) {
			Util.classy( this.sound_button, 'sound_off' );
			this.sound_button.setAttribute( 'sound', 'false' );
		} else {
			Util.classy( this.sound_button, 'sound_on' );
			this.sound_button.setAttribute( 'sound', 'true' );
		}
	};

	this.buyIt = function() {
		if ( this.score > this.buy ) {
			this.score -= this.buy;
			this.buy += 100;
			this.newBoard();
			this.updateFields();
		} else {
			this.error( 'sorry, you need ' + ( this.buy - this.score ) + ' more points for a buy...' );
		}
	};

	this.newBoard = function() {
		for ( var i = 0 ; i < this.getRowCount() ; i++ ) {
			for ( var j = 0 ; j < this.getColumnCount() ; j++ ) {
				this.randomColorCell( this.cellAt( i, j ) );
			}
		}
		this.animateFall();
	};

	this.randomColorCell = function( cell ) {
		this.colorCell( cell, Util.randomArrayEntry( this.colors ) );
	};

	this.cellColor = function( cell ) {
		return cell.getAttribute( 'fall_color' );
	};

	this.colorCell = function( cell, color ) {
		cell.setAttribute( 'fall_color', color );
		Util.classy( cell, 'ball_' + color );
	};

	this.makeHot = function( i, j, oops ) {
		this.hot = {i:Math.floor(i),j:Math.floor(j)};
		this.cellAt( i, j ).parentNode.setAttribute( 'id', 'fall_cell_hot' );
		if ( !oops ) {
			this.playSound( 'hot' );
		}
		Util.log( 'makeHot: ' + i + ',' + j );
	};

	this.clearHot = function() {
		if ( this.hot ) { 
			this.cellAt( this.hot.i, this.hot.j ).parentNode.removeAttribute( 'id' );
			this.hot = false;
			Util.log( 'clear' );
		}
	};

	this.isAdjacent = function( i,j, a,b ) {
		var k1 = ( i == a && j - 1 == b ); // right
		var k2 = ( i == a && j == b - 1 ); // left
		var k3 = ( j == b && i - 1 == a ); // up
		var k4 = ( j == b && i == a - 1 ); // down
		var adj = k1 || k2 || k3 || k4;
		return adj;
	};


	this.SOUTH = 0;
   	this.NORTH = 1;
	this.EAST = 2; 
	this.WEST = 3;

	this.dirFromDiff = function( i, j ) {
		return (
			0 == i
			? ( 1 == j ? this.EAST : this.WEST ) 
			: ( 1 == i ? this.SOUTH : this.NORTH )
		);
	};

	this.dirToName = function( dir ) {
		var word = '???';
		switch ( dir ) {
			case this.SOUTH : word = 'SOUTH'; break;
			case this.NORTH : word = 'NORTH'; break;
			case this.EAST  : word = 'EAST'; break; 
			case this.WEST  : word = 'WEST'; break;
		}
		return word;
	};

	this.threeFun = function( newColor, i1,j1, i2, j2 ) {
		return this.sameColorString( 
			newColor
			, this.cellColor( this.cellAt( i1, j1 ) )
			, this.cellColor( this.cellAt( i2, j2 ) )
		);
	};

	this.threeFrom = function( newColor, i, j, not_di, not_dj ) {
		var dir = this.dirFromDiff( not_di, not_dj );

		if ( Util.debug ) {
			var word = this.dirToName( dir );
			Util.log( '-------------------------------------------' );
			Util.log( 'threeFrom=' + newColor + '@(' + i + ',' + j + ' but not (' + not_di + ',' + not_dj + ') skip ' + dir + '(' + word + ')' );
		}

		var r = this.getRowCount();
		var c = this.getColumnCount();
		var ok = false;
		if ( dir != this.WEST && j >= 2 ) {
			if ( Util.debug ) Util.log( 'west...' );
			var k = this.threeFun( newColor, i,j-1, i,j-2 );
			if ( k ) return true;
			ok |= k;
			if ( Util.debug ) Util.log( 'try west '  + newColor +  ':' + k );
		}
		if ( dir != this.EAST && j < c - 2 ) {
			if ( Util.debug ) Util.log( 'east...' );
			var k = this.threeFun( newColor, i,j+1, i,j+2 );
			if ( k ) return true;
			ok |= k;
			if ( Util.debug ) Util.log( 'try east '  + newColor +  ':' + k );
		}
		if ( dir != this.SOUTH && i < r - 2 ) {
			if ( Util.debug ) Util.log( 'south... for ' + newColor + ' from the ' + word );
			var k = this.threeFun( newColor, i+1,j, i+2,j );
			if ( k ) return true;
			ok |= k;
			if ( Util.debug ) Util.log( 'try south ' + newColor +  ':' + k );
		}
		if ( dir != this.NORTH && i >= 2 ) {
			if ( Util.debug ) Util.log( 'north...' );
			var k = this.threeFun( newColor, i-1,j, i-2,j );
			if ( k ) return true;
			ok |= k;
			if ( Util.debug ) Util.log( 'try north ' + newColor +  ':' + k );
		}

		if ( this.WEST == dir || this.EAST == dir ) {
			if ( 0 != i && i < r - 1 ) {
				var k = this.threeFun( newColor, i-1,j, i+1,j );
				if ( k ) return true;
				//Util.log( 'NS middle k=' + k );
				ok |= k;
			}
		}

		if ( this.SOUTH == dir || this.NORTH == dir ) {
			if ( 0 != j && j < c - 1 ) {
				var k = this.threeFun( newColor, i,j-1, i,j+1 );
				if ( k ) return true;
				//Util.log( 'EW middle k=' + k );
				ok |= k;
			}
		}
		return ok;
	};

	this.moveThatClears = function( hi,hj, i, j ) {
		var hot_color = this.cellColor( this.cellAt( hi, hj ) );
		var not_color = this.cellColor( this.cellAt( i, j ) );
		//Util.log( 'hot_color=' + hot_color + ' and not_color=' + not_color );
		return ( 
			hot_color != not_color 
			&& ( false 
				|| this.threeFrom( hot_color, i,   j, hi -  i, hj -  j )
				|| this.threeFrom( not_color, hi, hj,  i - hi,  j - hj )
			)
		);
	};

	this.willClear = function( i, j ) {
		return this.moveThatClears( this.hot.i,this.hot.j, i,j );
	};

	this.canMove = function( i, j ) {
		return (
			this.hot
			&& this.isAdjacent( i, j, this.hot.i, this.hot.j )
			&& this.willClear( i, j, this.hot.i, this.hot.j )
		);
	};

	this.scoreCard = function( fell ) {
		var gain = 0;
		if ( 0 != fell && !this.new_board ) {
			var f = fell;
			if ( fell > 4 ) {
				this.playSound( 'applause' );
			}
			if ( fell > 6 ) f = 6;
			gain = Math.pow( 2, f ) + 10 * fell;
			this.score += gain;

			var d = this.score_popup;
			d.innerHTML = '+' + gain;// + 'x' + fell;
			Util.log( 'gain ' + gain + ' from ' + fell  );
			TimedUtil.fadeInElement( d, function() { TimedUtil.fadeOutElement( d ) } );
		}
		Util.log( 'fell=' + fell + ' score=' + this.score );
		this.updateScore();
		return gain;
	}

	this.move = function( i, j ) {
		var from = this.cellAt( this.hot.i, this.hot.j );
		var to = this.cellAt( i, j );
		var tmp = this.cellColor( to );
		this.colorCell( to, this.cellColor( from ) );
		this.colorCell( from, tmp );

		var fallen = this.willFall();//this.enforce();
		this.playSound( 'match' );

		this.animateBlowUp( this.scoreCard( fallen.length ), fallen );

		//this.animateFall();
		Util.log( 'move from ' + this.hot.i + ',' + this.hot.j + ' to ' + i + ',' + j );
	};

	this.resetTdClass = function( td ) {
		Util.classy( td, td.getAttribute( 'klass' ) );
	};

	this.resetTdClasses = function() { 
		var r = this.getRowCount();
		var c = this.getColumnCount();
		for ( var i = 0 ; i < r ; i++ ) {
			for ( var j = 0 ; j < c ; j++ ) {
				this.resetTdClass( this.cellAt( i, j ).parentNode );
			}
		}
	};
	
	this.clickedOn = function( i, j ) {
		this.busy = true;
		if ( this.hot ) {
			if ( i == this.hot.i && j == this.hot.j ) {
				this.clearHot();
			} else {
				if ( this.canMove( i, j ) ) {
					this.move( i, j );
					this.clearHot();
					this.resetTdClasses();
				} else {
					this.clearHot();
					//this.playSound( 'hammer' );
					this.makeHot( i, j, true );
				}
			}
		} else {
			this.makeHot( i, j );
		}
		this.busy = false;
	};
	
	this.click = function( e ) {
		if ( !this.busy ) {
			var pos = MouseUtil.getMouseXY( e );
			var target = pos.target;
			if ( target && Util.isTag( target, 'DIV' ) ) {
				var i = Math.floor( target.getAttribute( 'fall_i' ) );
				var j = Math.floor( target.getAttribute( 'fall_j' ) );
				this.clickedOn( i, j );
			}
		}
	};

	this.cellAt = function( i, j ) {
		return this.cells[ i ][ j ];
		//return this.cells[ i + '_' + j ];
	};

	this.sameColorString = function( a, b, c ) {
		return ( a == b && b == c );
	};

	this.sameColorCells = function( a, b, c ) {
		return ( 
			this.sameColorString( this.cellColor( a ), this.cellColor( b ), this.cellColor( c ) )
			? [ a, b, c ] 
			: []
		);
	};

	this.kellAt = function( i,j, swapI, swapJ, swapA, swapB ) {
		if ( i == swapI && j == swapJ ) {
			i = swapA;
			j = swapB;
		} else
		if ( i == swapA && j == swapB ) {
			i = swapI;
			j = swapJ;
		}
		return this.cellAt( i, j );
	};

	this.sameColor = function( i,j, k,l, m,n, swapI,swapJ, swapA,swapB ) {
		return this.sameColorCells( 
			  this.kellAt( i, j, swapI,swapJ, swapA,swapB )
			, this.kellAt( k, l, swapI,swapJ, swapA,swapB )
			, this.kellAt( m, n, swapI,swapJ, swapA,swapB ) 
		);
	};

	this.willFall = function( go_home_early, swapI, swapJ, swapA, swapB ) {

		var closing_time = false;
		var fallen = [];
		var r = this.getRowCount();
		var c = this.getColumnCount();
		for ( var i = 0 ; i < r ; i++ ) {
			if ( closing_time ) break;

			for ( var j = 0 ; j < c ; j++ ) {
				if ( closing_time ) break;
				if ( 0 != i && this.getRowCount() - 1 != i ) {
					fallen = fallen.concat( this.sameColor( i-1,j, i,j, i+1,j,  swapI, swapJ, swapA, swapB ) );
				}
			
				if ( go_home_early && 0 != fallen.length ) {
					closing_time = true;
					break;
				}

				if ( 0 != j && this.getColumnCount() - 1 != j ) {
					fallen = fallen.concat( this.sameColor( i,j-1, i,j, i,j+1, swapI, swapJ, swapA, swapB ) );
				}

				if ( go_home_early && 0 != fallen.length ) {
					closing_time = true;
					break;
				}
			}
		}
		return fallen;
	};

	this.enforce = function() {
		var fallen = this.willFall();
		for ( var i = 0 ; i < fallen.length ; i++ ) {
			this.colorCell( fallen[ i ], 'empty' );
		} 
		return fallen;
	};

	this.updateScore = function() {
		var score_field = this.score_field;
		var t = ''  + this.score;
		score_field.replaceChild( Util.newText( t ) , score_field.firstChild );
	};

	this.updateBuy = function() {
		var buy_field = this.buy_field;
		var t = ''  + this.buy;
		buy_field.replaceChild( Util.newText( t ) , buy_field.firstChild );
	};

	this.updateFields = function() {
		this.updateScore();
		this.updateBuy();
	};

	this.doneFalling = function() {
		var fell = this.enforce().length;
		if ( 0 != fell ) {
			this.scoreCard( fell );
			this.animateFall();
		} else {
			this.new_board = false;
			//this.playSound( 'blip1' );
		}
		this.busy = false;
	};

	var MoveBackgroundTimerTask = function( parent ) {
		this.bgz = [
			  'transparent url( images/bg/poly.jpg )    repeat scroll ' 
			, 'transparent url( images/bg/pipe.png )    repeat scroll ' 
			, 'transparent url( images/bg/comrade.png ) repeat scroll ' 
			, 'transparent url( images/bg/starry.jpg )  repeat scroll ' 
		];
		this.sprkz = [
			'images/sparkles/SparkleRed-1.gif'
			, 'images/sparkles/sparkles.gif'
		];
		this.run = function( tick ) {
			if ( this.when && tick - this.when > this.next ) {
				this.which.innerHTML = ''; /* removeChild was throwing a tantrum... */
			}
			if ( 0 == tick % 23 ) {
				var i = Math.floor( parent.getRowCount() * Math.random() );
				var j = Math.floor( parent.getColumnCount() * Math.random() );
				this.which = parent.cellAt( i, j );
				this.which.appendChild( 
					Util.newNode( 'img', {src: Util.randomArrayEntry( this.sprkz )} )
				);
				this.when = tick;
				this.next = 5 + Math.random() * 5;
			}
			if ( 0 == tick % 2 ) {
				var node = parent.fall_main;
				var offset = Math.floor( node.getAttribute( 'bg_offset' ) );
				offset = 4 + Math.floor( offset );
				node.setAttribute( 'bg_offset', offset );
				node.style.background = (
					this.bgz[
						Math.floor( parent.score / 1000 ) % this.bgz.length
					]
					+ offset + 'px ' 
					+ offset + 'px'
				);
			}
			return true;
		}
	};

	var FallTimerTask = function( parent ) {
		this.parent = parent;
		this.run = function( tick ) {
			if ( 0 != tick % 2 ) return true;
			var fell = false;

			for ( var i = 0 ; i < parent.getColumnCount() ; i++ ) {
				if ( parent.cellColor( parent.cellAt( 0, i ) ).match( /.*empty.*/ ) ){
					parent.randomColorCell( parent.cellAt( 0, i ) );
					fell = true;
					continue;
				}
				for ( j = 0 ; j < parent.getRowCount() - 1 ; j++ ) {
					var cell = parent.cellAt( j, i );
					var down = parent.cellAt( j+1, i );
					if ( parent.cellColor( down ).match( /.*empty.*/ ) ) {
						var tmp = parent.cellColor( down );
						parent.colorCell( down, parent.cellColor( cell ) );
						parent.colorCell( cell, tmp );
						fell = true;
					}
				}
			}

			if ( !fell ) {
				parent.doneFalling();
			}
			return fell;
		}
	};

	this.fallTimerTask = new FallTimerTask( this );

	this.animateFall = function() {
		this.busy = true;
		this.fell = 0;
		Timer.addTask( this.fallTimerTask );
	};

	this.doneBlowUp = function() {
		this.animateFall();
	};

	this.playSound = function( sound ) {
		if ( this.isSoundOn() ) {
			var snd = Util.newNode(
				'embed'
				, {autostart:'true',hidden:'true',src:'sounds/' + sound + '.wav'} 
			);
			this.sound.innerHTML = '';
			this.sound.appendChild( snd );
		}
	};

	var BlowUpTimerTask = function( parent ) {
		this.parent = parent;
		this.started = 0;
		this.run = function( tick ) {
			var still_going = true;
			if ( !this.started ) {
				this.started = tick;
			} else {
				var diff = tick - this.started;
				if ( diff > 10 ) {
					for ( var i = 0 ; i < this.fallen.length ; i++ ) {
						Util.opaque( this.fallen[ i ], 1 );
						parent.colorCell( this.fallen[ i ], 'empty' );
					}
					this.started = 0;
					still_going = false;
				} else { 
					for ( var i = 0 ; i < this.fallen.length ; i++ ) {
						Util.opaque( this.fallen[ i ], 1 - ( diff / 10 ) );
					}
				}
			}

			if ( !still_going ) {
				parent.doneBlowUp();
			}

			return still_going;
		}
	};

	this.blowUpTimerTask = new BlowUpTimerTask( this );

	this.animateBlowUp = function( gain, fallen ) {
		this.busy = true;
		this.blowUpTimerTask.gain = gain;
		this.blowUpTimerTask.fallen = fallen;
		Timer.addTask( this.blowUpTimerTask );
	}

	this.hint = function() { 
		if ( !this.busy && !this.hint_button.getAttribute( 'disabled' ) ) {
			var wuz = Util.debug;
			Util.debug = false;
			this.busy = true;

			this.hint_button.setAttribute( 'disabled', true );
			var r = this.getRowCount();
			var c = this.getColumnCount();
			var choices = [];
			for ( var i = 0 ; i < r -1; i++ ) {
				for ( var j = 0 ; j < c-1 ; j++ ) {
					for ( var di = i ; di < i + 2 ; di++ ) {
						for ( var dj = j ; dj < j + 2 ; dj++ ) {
							if ( di == i && dj == j ) continue;
							if ( di != i && dj != j ) continue;

							if ( this.moveThatClears( i,j, di, dj ) ) {
								choices.push( {i:i,j:j} );
								//Util.classy( this.cellAt( i, j ).parentNode, 'fall_hint' );
								Util.log( 'hint: ' + i + ',' + j + ' move to ' + di +',' + dj );
							}
						}
					}
				}
			}
			Util.log( 'hint found ' + choices );
			if ( 0 == choices.length ) {
				this.error( "sorry, I don't see any moves either... can you try a buy?" );
			} else {
				var choice = Util.randomArrayEntry( choices );
				//Util.classy( this.cellAt( choice.i, choice.j ).parentNode, 'fall_hint' );
				this.clearHot();
				this.makeHot( choice.i, choice.j, true );
			}

			Util.debug = wuz;
			
			this.busy = false;
			this.hint_button.removeAttribute( 'disabled' );
		}
		Util.log( 'hint stopped' );
	};

	this.error = function( s ) {
		//alert( s );
		this.notify.innerHTML = s;
		Util.show( this.notify_div );
	};

	this.newGame = function() {
		this.new_board = true;
		this.newBoard();
		this.score = 0;
		this.buy = 100;
		this.updateFields();
	};

	this.init = function( id, rowCount, columnCount ){
		this.setId( id );
		this.setRowCount( rowCount );
		this.setColumnCount( columnCount );
		this.createBoard();
		this.newGame();
		Timer.addTask( new MoveBackgroundTimerTask( this ) );
	};

	this.init( id, rowCount, columnCount );
};
